// Copyright 2019 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Tests minfs inspector behavior.

#include <minfs/inspector.h>

#include <lib/disk-inspector/disk-inspector.h>
#include <zxtest/zxtest.h>

#include "inspector-private.h"
#include "minfs-private.h"

namespace minfs {
namespace {

// Mock InodeManager class to be used in inspector tests.
class MockInodeManager : public InspectableInodeManager {
 public:
  MockInodeManager();
  MockInodeManager(const MockInodeManager&) = delete;
  MockInodeManager(MockInodeManager&&) = delete;
  MockInodeManager& operator=(const MockInodeManager&) = delete;
  MockInodeManager& operator=(MockInodeManager&&) = delete;

  void Load(ino_t inode_num, Inode* out) const final;
  const Allocator* GetInodeAllocator() const final;
};

MockInodeManager::MockInodeManager() {}

void MockInodeManager::Load(ino_t inode_num, Inode* out) const {}

const Allocator* MockInodeManager::GetInodeAllocator() const { return nullptr; }

constexpr Superblock superblock = {};

// Mock Minfs class to be used in inspector tests.
class MockMinfs : public InspectableFilesystem {
 public:
  MockMinfs() = default;
  MockMinfs(const MockMinfs&) = delete;
  MockMinfs(MockMinfs&&) = delete;
  MockMinfs& operator=(const MockMinfs&) = delete;
  MockMinfs& operator=(MockMinfs&&) = delete;

  const Superblock& Info() const { return superblock; }

  const InspectableInodeManager* GetInodeManager() const final { return nullptr; }

  const Allocator* GetBlockAllocator() const final { return nullptr; }

  zx_status_t ReadBlock(blk_t start_block_num, void* out_data) const final { return ZX_OK; }
};

TEST(InspectorTest, TestRoot) {
  auto fs = std::unique_ptr<MockMinfs>(new MockMinfs());

  std::unique_ptr<RootObject> root_obj(new RootObject(std::move(fs)));
  ASSERT_STR_EQ(kRootName, root_obj->GetName());
  ASSERT_EQ(kRootNumElements, root_obj->GetNumElements());

  // Superblock.
  std::unique_ptr<disk_inspector::DiskObject> obj0 = root_obj->GetElementAt(0);
  ASSERT_STR_EQ(kSuperBlockName, obj0->GetName());
  ASSERT_EQ(kSuperblockNumElements, obj0->GetNumElements());

  // Inode Table.
  std::unique_ptr<disk_inspector::DiskObject> obj1 = root_obj->GetElementAt(1);
  ASSERT_STR_EQ(kInodeTableName, obj1->GetName());

  // Journal info.
  std::unique_ptr<disk_inspector::DiskObject> obj2 = root_obj->GetElementAt(2);
  ASSERT_STR_EQ(kJournalName, obj2->GetName());
  ASSERT_EQ(kJournalNumElements, obj2->GetNumElements());
}

TEST(InspectorTest, TestInodeTable) {
  auto inode_table_obj = std::unique_ptr<MockInodeManager>(new MockInodeManager());

  std::unique_ptr<InodeTableObject> inode_mgr(new InodeTableObject(inode_table_obj.get(), 2));
  ASSERT_STR_EQ(kInodeTableName, inode_mgr->GetName());
  ASSERT_EQ(2, inode_mgr->GetNumElements());

  std::unique_ptr<disk_inspector::DiskObject> obj0 = inode_mgr->GetElementAt(0);
  ASSERT_STR_EQ(kInodeName, obj0->GetName());
  ASSERT_EQ(kInodeNumElements, obj0->GetNumElements());

  std::unique_ptr<disk_inspector::DiskObject> obj1 = inode_mgr->GetElementAt(1);
  ASSERT_STR_EQ(kInodeName, obj1->GetName());
  ASSERT_EQ(kInodeNumElements, obj1->GetNumElements());
}

TEST(InspectorTest, TestSuperblock) {
  Superblock sb;
  sb.magic0 = kMinfsMagic0;
  sb.magic1 = kMinfsMagic1;
  sb.version_major = kMinfsMajorVersion;
  sb.version_minor = kMinfsMinorVersion;
  sb.flags = kMinfsFlagClean;
  sb.block_size = kMinfsBlockSize;
  sb.inode_size = kMinfsInodeSize;

  size_t size;
  const void* buffer = nullptr;

  std::unique_ptr<SuperBlockObject> superblock(new SuperBlockObject(sb));
  ASSERT_STR_EQ(kSuperBlockName, superblock->GetName());
  ASSERT_EQ(kSuperblockNumElements, superblock->GetNumElements());

  std::unique_ptr<disk_inspector::DiskObject> obj0 = superblock->GetElementAt(0);
  obj0->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsMagic0, *(reinterpret_cast<const uint64_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj1 = superblock->GetElementAt(1);
  obj1->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsMagic1, *(reinterpret_cast<const uint64_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj2 = superblock->GetElementAt(2);
  obj2->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsMajorVersion, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj3 = superblock->GetElementAt(3);
  obj3->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsMinorVersion, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj4 = superblock->GetElementAt(4);
  obj4->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsFlagClean, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj5 = superblock->GetElementAt(5);
  obj5->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsBlockSize, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj6 = superblock->GetElementAt(6);
  obj6->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsInodeSize, *(reinterpret_cast<const uint32_t*>(buffer)));
}

TEST(InspectorTest, TestJournal) {
  fs::JournalInfo jinfo;
  jinfo.magic = fs::kJournalMagic;
  auto info = std::make_unique<fs::JournalInfo>(jinfo);

  std::unique_ptr<JournalObject> journalObj(new JournalObject(std::move(info)));
  ASSERT_STR_EQ(kJournalName, journalObj->GetName());
  ASSERT_EQ(kJournalNumElements, journalObj->GetNumElements());

  size_t size;
  const void* buffer = nullptr;

  std::unique_ptr<disk_inspector::DiskObject> obj0 = journalObj->GetElementAt(0);
  obj0->GetValue(&buffer, &size);
  ASSERT_EQ(fs::kJournalMagic, *(reinterpret_cast<const uint64_t*>(buffer)));
}

TEST(InspectorTest, TestInode) {
  Inode fileInode;
  fileInode.magic = kMinfsMagicFile;
  fileInode.size = 10;
  fileInode.block_count = 2;
  fileInode.link_count = 1;

  std::unique_ptr<InodeObject> finodeObj(new InodeObject(fileInode));
  ASSERT_STR_EQ(kInodeName, finodeObj->GetName());
  ASSERT_EQ(kInodeNumElements, finodeObj->GetNumElements());

  size_t size;
  const void* buffer = nullptr;

  std::unique_ptr<disk_inspector::DiskObject> obj0 = finodeObj->GetElementAt(0);
  obj0->GetValue(&buffer, &size);
  ASSERT_EQ(kMinfsMagicFile, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj1 = finodeObj->GetElementAt(1);
  obj1->GetValue(&buffer, &size);
  ASSERT_EQ(10, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj2 = finodeObj->GetElementAt(2);
  obj2->GetValue(&buffer, &size);
  ASSERT_EQ(2, *(reinterpret_cast<const uint32_t*>(buffer)));

  std::unique_ptr<disk_inspector::DiskObject> obj3 = finodeObj->GetElementAt(3);
  obj3->GetValue(&buffer, &size);
  ASSERT_EQ(1, *(reinterpret_cast<const uint32_t*>(buffer)));
}

}  // namespace
}  // namespace minfs
